﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Runtime.Caching;
using Sustainalytics.Admin.Synchronization.Utility;
using Sustainalytics.ClientDW.DataAccessLayer;
using Sustainalytics.ClientDW.Entities;
using Sustainalytics.CompanyScreening.Exceptions;
using Sustainalytics.CompanyScreening.Model;
using Sustainalytics.DataLayer;
using Sustainalytics.Entities;
using Sustainalytics.Utils;

namespace Sustainalytics.CompanyScreening
{
    public enum CriteriaEntityField
    {
        CompanyName,
        RootPeerGroup,
        Region,
        Country,
        CompanyType,
        GICSSector,
        GICSIndustryGroup,
        GICSIndustry,
        GICSSubIndustry,
        TotalEmployees,
        MarketCapitalization,
        TotalRevenues,
        NetIncome,
        NEBT,
        Exchanges,
        Tickers,
        ISINs,
        Sedols,
        CUSIPs,
        TotalEsgScore,
        EnvironmentScore,
        SocialScore,
        GovernanceScore,
        PreparednessScore,
        DisclosureScore,
        QualitativePerformanceScore,
        QuantitativePerformanceScore
    }

    public class CompanyScreening : ICompanyScreening
    {
        #region Fields

        private IAccountsRepository _accountsRepository;
        private IClientDwRepository _clientDWRepository;
        private Dictionary<Type, object> _cloneMethods = new Dictionary<Type, object>();
        private ICompanyScreeningRepository _companyScreeningRepository;
        private IPortfolioCompanyRepository _portfolioCompanyRepository;

        #endregion Fields

        #region Constructors

        private CompanyScreening()
        {
            AddFunc<CriteriaTypeOne>(this._cloneMethods, t => this.CreateCriteria(t));
            AddFunc<CriteriaTypeTwo>(this._cloneMethods, t => this.CreateCriteria(t));
            AddFunc<CriteriaTypeThree>(this._cloneMethods, t => this.CreateCriteria(t));
            AddFunc<CriteriaTypeFive>(this._cloneMethods, t => this.CreateCriteria(t));
            AddFunc<CriteriaTypeSix>(this._cloneMethods, t => this.CreateCriteria(t));
        }

        #endregion Constructors

        #region factory methods

        public static ICompanyScreening GetInstance(ICompanyScreeningRepository companyScreeningRepository, IClientDwRepository clientDWRepository, IAccountsRepository accountsRepository, IPortfolioCompanyRepository portfolioCompanyRepository)
        {
            return new CompanyScreening
            {
                _companyScreeningRepository = companyScreeningRepository,
                _clientDWRepository = clientDWRepository,
                _accountsRepository = accountsRepository,
                _portfolioCompanyRepository = portfolioCompanyRepository
            };
        }

        #endregion

        #region add criteria
        public CriteriaTypeFive AddCriteriaTypeFive(Guid templateId, int? score, string templateIndicatorNumber, string username)
        {
            CompanyScreeningEventSource.Log.AddCriteriaTypeFiveService(templateId, username);

            var templateCriteria = _companyScreeningRepository.LoadTemplateCriteriaTypeFive(templateId);
            var filter = FindCurrentFilterAndIncrementVersion(username);

            var criteriaTypeFive = new CriteriaTypeFive()
            {
                Id = Guid.NewGuid(),
                Name = templateCriteria.Name,
                Score = score,

                CriteriaEntityField = templateCriteria.CriteriaEntityField,
                CriteriaEntity = templateCriteria.CriteriaEntity,
                TemplateIndicatorNumber = templateCriteria.TemplateIndicatorNumber,

                CompaniesIdsResult = null,
                CriteriaLevel2Id = templateCriteria.CriteriaLevel2Id,
                CriteriaType = CriteriaTypeEnum.CriteriaType5,
                FilterId = filter.FilterId
            };

            _companyScreeningRepository.AddCriteriaTypeFive(criteriaTypeFive);

            var indicatorTemplate = _clientDWRepository.GetIndicatorTemplate(criteriaTypeFive.TemplateIndicatorNumber);
            if (indicatorTemplate != null)
                criteriaTypeFive.AnswerCategories = indicatorTemplate.AnswerCategories;

            return criteriaTypeFive;
        }

        public CriteriaTypeOne AddCriteriaTypeOne(Guid templateId, int minValue, int maxValue, string username)
        {
            try
            {
                var templateCriteria = this.LoadCriteriaTypeOne(templateId);
                var currentFilter = FindCurrentFilterAndIncrementVersion(username);
           
                CompanyScreeningEventSource.Log.GetCurrentFilterSuccess(username);

                var criteriaTypeOne = CreateCriteriaTypeOne(minValue, maxValue, templateCriteria, currentFilter.FilterId);

                _companyScreeningRepository.AddCriteriaTypeOne(criteriaTypeOne);

                return criteriaTypeOne;
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

        private CurrentFilter FindCurrentFilterAndIncrementVersion(string username)
        {
            var user = _accountsRepository.GetUserByUsername(username);
            if (user == null)
                throw new UnknownUserException { UserName = username };

            var currentFilter = _companyScreeningRepository.FindAndIncrementVersion<CurrentFilter>(user.Id);
            return currentFilter;
        }

        public CriteriaTypeThree AddCriteriaTypeThree(Guid templateId, string collectionName, List<string> selectedItems, string username)
        {
            //CompanyScreeningEventSource.Log.AddCriteriaTypeThreeService(templateId, username);

            CriteriaTypeThree templateCriteria = _companyScreeningRepository.LoadCriteriaTypeThree(templateId);
            var currentFilter = FindCurrentFilterAndIncrementVersion(username);

            var criteriaTypeThree = new CriteriaTypeThree()
            {
                Id = Guid.NewGuid(),
                Name = templateCriteria.Name,
                CollectionName = collectionName,
                SelectedItems = selectedItems,
                CriteriaEntityField = templateCriteria.CriteriaEntityField,
                CriteriaEntity = templateCriteria.CriteriaEntity,
                CompaniesIdsResult = null,
                CriteriaLevel2Id = templateCriteria.CriteriaLevel2Id,
                CriteriaType = CriteriaTypeEnum.CriteriaType3,
                FilterId = currentFilter.FilterId
            };

            _companyScreeningRepository.AddCriteriaTypeThree(criteriaTypeThree);

            return criteriaTypeThree;
        }

        public CriteriaTypeTwo AddCriteriaTypeTwo(Guid templateId, int? score, string username)
        {
            CompanyScreeningEventSource.Log.AddCriteriaTypeTwoService(templateId, username);

            var templateCriteria = _companyScreeningRepository.LoadCriteriaTypeTwo(templateId);
            var currentFilter = FindCurrentFilterAndIncrementVersion(username);

            var criteriaTypeTwo = new CriteriaTypeTwo()
            {
                Id = Guid.NewGuid(),
                Name = templateCriteria.Name,
                Score = score,
                BaseEventIndicatorId = templateCriteria.BaseEventIndicatorId,
                CriteriaEntityField = templateCriteria.CriteriaEntityField,
                CriteriaEntity = templateCriteria.CriteriaEntity,
                CompaniesIdsResult = null,
                CriteriaLevel2Id = templateCriteria.CriteriaLevel2Id,
                CriteriaType = CriteriaTypeEnum.CriteriaType2,
                FilterId = currentFilter.FilterId
            };

            _companyScreeningRepository.AddCriteriaTypeTwo(criteriaTypeTwo);

            return criteriaTypeTwo;
        }

        public CriteriaTypeSix AddCriteriaTypeSix(Guid templateId, int minValue, int maxValue, string username)
        {
            try
            {
                var templateCriteria = this.LoadCriteriaTypeSix(templateId);
                var currentFilter = FindCurrentFilterAndIncrementVersion(username);

                CompanyScreeningEventSource.Log.GetCurrentFilterSuccess(username);

                var criteriaTypeSix = CreateCriteriaTypeSix(minValue, maxValue, Guid.Empty, templateCriteria, currentFilter.FilterId);

                _companyScreeningRepository.AddCriteriaTypeSix(criteriaTypeSix);

                return criteriaTypeSix;
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }
        #endregion

        #region delete criteria
        public void DeleteAllCriteria(string username, Guid filterId)
        {
            FindCurrentFilterAndIncrementVersion(username);
            _companyScreeningRepository.DeleteAllCriteria(filterId);
        }

        public void DeleteCriteria(string username, Guid criteriaId)
        {
            try
            {
                FindCurrentFilterAndIncrementVersion(username);
                _companyScreeningRepository.DeleteCriteria(criteriaId);
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }
        
        #endregion
    
        #region list criteria

        public object GetCriteriaByID(Guid criteriaId, int criteriaType)
        {
            try
            {
                CompanyScreeningEventSource.Log.GetCriteriaByID(criteriaId);

                var result = _companyScreeningRepository.GetCriteriaByID(criteriaId, criteriaType);

                return result;
            }
            catch (Exception ex)
            {
                CompanyScreeningEventSource.Log.GetCriteriaByIDError(criteriaId, ex);
                throw ex;
            }
        }


        public List<BaseCriteria> ListBaseCriteria()
        {
            try
            {
                CompanyScreeningEventSource.Log.ListBaseCriteria();
                var baseCriteria = _companyScreeningRepository.ListBaseCriteria(null);

                return baseCriteria;
            }
            catch (Exception ex)
            {
                CompanyScreeningEventSource.Log.ListBaseCriteriaError(ex);
                throw ex;
            }
        }

        public List<CriteriaLevel1> ListCriteriaLevelOne()
        {
            try
            {
                CompanyScreeningEventSource.Log.ListCriteriaLevelOne();
                var criteriaLevelOne = _companyScreeningRepository.ListCriteriaLevelOne();

                return criteriaLevelOne;
            }
            catch (Exception ex)
            {
                CompanyScreeningEventSource.Log.ListCriteriaLevelOneError(ex);
                throw ex;
            }
        }

        public List<CriteriaLevel2> ListCriteriaLevelTwo()
        {
            try
            {
                CompanyScreeningEventSource.Log.ListCriteriaLevelTwo();
                var criteriaLevelTwo = _companyScreeningRepository.ListCriteriaLevelTwo(null);

                return criteriaLevelTwo;
            }
            catch (Exception ex)
            {
                CompanyScreeningEventSource.Log.ListCriteriaLevelTwoError(ex);
                throw ex;
            }
        }

        #endregion 

        #region list selected criteria

        public List<CriteriaTypeFive> ListSelectedCriteriaTypeFive(Guid filterId)
        {
            var result = new List<CriteriaTypeFive>();
            try
            {
                result = _companyScreeningRepository.ListSelectedCriteriaTypeFive(filterId);

                foreach (var criteriaTypeFive in result)
                {
                    var indicatorTemplate = _clientDWRepository.GetIndicatorTemplate(criteriaTypeFive.TemplateIndicatorNumber);
                    if (indicatorTemplate != null)
                        criteriaTypeFive.AnswerCategories = indicatorTemplate.AnswerCategories;
                }
            }
            catch (Exception ex)
            {
                CompanyScreeningEventSource.Log.ListSelectedCriteriaTypeFiveError(filterId, ex);
                throw ex;
            }

            return result;
        }

        public List<CriteriaTypeOne> ListSelectedCriteriaTypeOne(Guid filterId)
        {
            try
            {
                return _companyScreeningRepository.ListSelectedCriteriaTypeOne(filterId);
            }
            catch (Exception ex)
            {
                CompanyScreeningEventSource.Log.ListSelectedCriteriaTypeOneError(filterId, ex);
                throw ex;
            }
        }

        public List<CriteriaTypeThree> ListSelectedCriteriaTypeThree(Guid filterId)
        {
            try
            {
                return _companyScreeningRepository.ListSelectedCriteriaTypeThree(filterId);
            }
            catch (Exception ex)
            {
                CompanyScreeningEventSource.Log.ListSelectedCriteriaTypeThreeError(filterId, ex);
                throw ex;
            }
        }

        public List<CriteriaTypeTwo> ListSelectedCriteriaTypeTwo(Guid filterId)
        {
            try
            {
                return _companyScreeningRepository.ListSelectedCriteriaTypeTwo(filterId);
            }
            catch (Exception ex)
            {
                CompanyScreeningEventSource.Log.ListSelectedCriteriaTypeTwoError(filterId, ex);
                throw ex;
            }
        }

        public List<CriteriaTypeSix> ListSelectedCriteriaTypeSix(Guid filterId)
        {
            try
            {
                return _companyScreeningRepository.ListSelectedCriteriaTypeSix(filterId);
            }
            catch (Exception ex)
            {
                CompanyScreeningEventSource.Log.ListSelectedCriteriaTypeSixError(filterId, ex);
                throw ex;
            }
        }

        #endregion 

        #region load criteria

        public CriteriaTypeFive LoadCriteriaTypeFive(Guid criteriaTemplateId)
        {
            CompanyScreeningEventSource.Log.LoadCriteriaTypeFiveService(criteriaTemplateId);
            var criteria = _companyScreeningRepository.LoadCriteriaTypeFive(criteriaTemplateId);

            var indicatorTemplate = _clientDWRepository.GetIndicatorTemplate(criteria.TemplateIndicatorNumber);
            if (indicatorTemplate != null)
                criteria.AnswerCategories = indicatorTemplate.AnswerCategories;

            return criteria;
        }

        public CriteriaTypeOne LoadCriteriaTypeOne(Guid criteriaTemplateId)
        {
            try
            {
                var criteriaTypeOne = _companyScreeningRepository.LoadCriteriaTypeOne(criteriaTemplateId);
                return criteriaTypeOne;
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

        public List<CriteriaChartData> LoadCriteriaTypeOneData(Guid criteriaTemplateId)
        {
            try
            {
                var criteriaTypeOne = this.LoadCriteriaTypeOne(criteriaTemplateId);

                var clusters = ComputeClusters(criteriaTypeOne);

                return clusters;
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

        public CriteriaTypeThree LoadCriteriaTypeThree(Guid criteriaTemplateId)
        {
            try
            {
                //CompanyScreeningEventSource.Log.LoadCriteriaTypeThreeService(criteriaTemplateId);
                var criteriaTypeThree = _companyScreeningRepository.LoadCriteriaTypeThree(criteriaTemplateId);
                return criteriaTypeThree;
            }
            catch (Exception ex)
            {
                //CompanyScreeningEventSource.Log.LoadCriteriaTypeThreeServiceError(criteriaTemplateId, ex);
                throw ex;
            }
        }

        public List<string> LoadCriteriaTypeThreeData(Guid templateId, int skip, int take, string filter, out int totalCount)
        {
            var criteriaTypeThree = LoadCriteriaTypeThree(templateId);
            List<string> data = _clientDWRepository.LoadCriteriaTypeThreeData(criteriaTypeThree.CriteriaEntity, criteriaTypeThree.CriteriaEntityField, criteriaTypeThree.CollectionName, new List<int>(), skip, take, filter, out totalCount);
            return data;
        }

        public CriteriaTypeTwo LoadCriteriaTypeTwo(Guid criteriaTemplateId)
        {
            CompanyScreeningEventSource.Log.LoadCriteriaTypeTwoService(criteriaTemplateId);
            return _companyScreeningRepository.LoadCriteriaTypeTwo(criteriaTemplateId);
        }

        public CriteriaTypeSix LoadCriteriaTypeSix(Guid criteriaTemplateId)
        {
            try
            {
                var criteriaTypeSix = _companyScreeningRepository.LoadCriteriaTypeSix(criteriaTemplateId);
                return criteriaTypeSix;
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

        public List<CriteriaChartData> LoadCriteriaTypeSixData(Guid criteriaTemplateId)
        {
            try
            {
                var criteriaTypeSix = this.LoadCriteriaTypeSix(criteriaTemplateId);

                return ComputeCriteriaTypeSixData(criteriaTypeSix);;
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }
        #endregion

        #region load template criteria
        public CriteriaTypeFive LoadTemplateCriteriaTypeFive(Guid criteriaTemplateId)
        {
            CompanyScreeningEventSource.Log.LoadTemplateCriteriaTypeFiveService(criteriaTemplateId);
            var criteria = _companyScreeningRepository.LoadTemplateCriteriaTypeFive(criteriaTemplateId);

            var indicatorTemplate = _clientDWRepository.GetIndicatorTemplate(criteria.TemplateIndicatorNumber);
            if (indicatorTemplate != null)
                criteria.AnswerCategories = indicatorTemplate.AnswerCategories;

            return criteria;
        }
        
        #endregion 

        #region process criteria
        public List<Guid> ProcessCriteriaTypeFive(CriteriaTypeFive criteria, Guid? portfolioId)
        {
            try
            {
                CompanyScreeningEventSource.Log.ProcessCriteriaTypeFive(criteria.CriteriaEntity,
                                                                       criteria.CriteriaEntityField);

                var results = _clientDWRepository.ListCriteriaTypeFiveResults(criteria.CriteriaEntity,
                                                                         criteria.CriteriaEntityField,
                                                                         criteria.Score, criteria.TemplateIndicatorNumber,
                                                                         new List<int>());
                if (portfolioId.HasValue)
                {
                    List<Guid> portfolioCompaniesIds = _portfolioCompanyRepository.GetCompaniesForPortfolio(portfolioId.Value).Select(a => a.CompanyId).ToList();

                    results = results.Where(e => portfolioCompaniesIds.Any(c => c.Equals(e))).ToList();
                }
                _companyScreeningRepository.UpdateCriteriaCompaniesIdsResult(criteria.Id, results);

                return results;
            }
            catch (Exception ex)
            {
                CompanyScreeningEventSource.Log.ProcessCriteriaTypeFiveError(criteria.CriteriaEntity,
                                                                       criteria.CriteriaEntityField,
                                                                       ex);
                throw;
            }
        }

        public List<Guid> ProcessCriteriaTypeOne(CriteriaTypeOne criteria, Guid? portfolioId)
        {
            try
            {
                CompanyScreeningEventSource.Log.ProcessCriteriaTypeOne(criteria.CriteriaEntity, criteria.CriteriaEntityField, criteria.MinValue, criteria.MaxValue);

                List<Guid> results = _clientDWRepository.ListCriteriaTypeOneResults(criteria.CriteriaEntity,
                                                                         criteria.CriteriaEntityField,
                                                                         criteria.MinValue,
                                                                         criteria.MaxValue,
                                                                         new List<int>());

                if (portfolioId.HasValue)
                {
                    List<Guid> portfolioCompaniesIds = _portfolioCompanyRepository.GetCompaniesForPortfolio(portfolioId.Value).Select(a=>a.CompanyId).ToList();

                    results = results.Where(e => portfolioCompaniesIds.Any(c=>c.Equals(e))).ToList();
                }
                
                _companyScreeningRepository.UpdateCriteriaCompaniesIdsResult(criteria.Id, results);

                return results;
            }
            catch (Exception ex)
            {
                CompanyScreeningEventSource.Log.ProcessCriteriaTypeOneError(criteria.CriteriaEntity, criteria.CriteriaEntityField, criteria.MinValue, criteria.MaxValue, ex);
                throw ex;
            }
        }

        public List<Guid> ProcessCriteriaTypeThree(CriteriaTypeThree criteria, Guid? portfolioId)
        {
            try
            {
                CompanyScreeningEventSource.Log.ProcessCriteriaTypeThree(criteria.CriteriaEntity,
                                                                       criteria.CriteriaEntityField);
                var results = _clientDWRepository.ListCriteriaTypeThreeResults(criteria.CriteriaEntity,
                                                                         criteria.CriteriaEntityField,
                                                                         criteria.SelectedItems,
                                                                         new List<int>());
                if (portfolioId.HasValue)
                {
                    List<Guid> portfolioCompaniesIds = _portfolioCompanyRepository.GetCompaniesForPortfolio(portfolioId.Value).Select(a => a.CompanyId).ToList();

                    results = results.Where(e => portfolioCompaniesIds.Any(c => c.Equals(e))).ToList();
                }
                _companyScreeningRepository.UpdateCriteriaCompaniesIdsResult(criteria.Id, results);

                return results;
            }
            catch (Exception ex)
            {
                CompanyScreeningEventSource.Log.ProcessCriteriaTypeThreeError(criteria.CriteriaEntity,
                                                                       criteria.CriteriaEntityField,
                                                                       ex);
                throw;
            }
        }

        public List<Guid> ProcessCriteriaTypeTwo(CriteriaTypeTwo criteria, Guid? portfolioId)
        {
            try
            {
                CompanyScreeningEventSource.Log.ProcessCriteriaTypeTwoService(criteria.CriteriaEntity,
                                                                         criteria.CriteriaEntityField);
                var results = _clientDWRepository.ListCriteriaTypeTwoResults(criteria.CriteriaEntity,
                                                                         criteria.CriteriaEntityField,
                                                                         criteria.BaseEventIndicatorId,
                                                                         criteria.Score,
                                                                         new List<int>());

                if (portfolioId.HasValue)
                {
                    List<Guid> portfolioCompaniesIds = _portfolioCompanyRepository.GetCompaniesForPortfolio(portfolioId.Value).Select(a => a.CompanyId).ToList();

                    results = results.Where(e => portfolioCompaniesIds.Any(c => c.Equals(e))).ToList();
                }
                _companyScreeningRepository.UpdateCriteriaCompaniesIdsResult(criteria.Id, results);

                return results;
            }
            catch (Exception ex)
            {
                CompanyScreeningEventSource.Log.ProcessCriteriaTypeTwoServiceError(ex.Message);
                throw;
            }
        }

        public List<Guid> ProcessCriteriaTypeSix(CriteriaTypeSix criteria, Guid? portfolioId)
        {
            try
            {
                CompanyScreeningEventSource.Log.ProcessCriteriaTypeSix(criteria.CriteriaEntity, criteria.CriteriaEntityField, criteria.MinValue, criteria.MaxValue, criteria.WeightMatrixId);

                var results = _clientDWRepository.ListCriteriaTypeSixResults(criteria.CriteriaEntity,
                                                                         criteria.CriteriaEntityField,
                                                                         criteria.MinValue,
                                                                         criteria.MaxValue,
                                                                         new List<int>(),
                                                                         criteria.WeightMatrixId);

                if (portfolioId.HasValue)
                {
                    List<Guid> portfolioCompaniesIds = _portfolioCompanyRepository.GetCompaniesForPortfolio(portfolioId.Value).Select(a => a.CompanyId).ToList();

                    results = results.Where(e => portfolioCompaniesIds.Any(c => c.Equals(e))).ToList();
                }

                _companyScreeningRepository.UpdateCriteriaCompaniesIdsResult(criteria.Id, results);

                return results;
            }
            catch (Exception ex)
            {
                CompanyScreeningEventSource.Log.ProcessCriteriaTypeSixError(criteria.CriteriaEntity, 
                    criteria.CriteriaEntityField, 
                    criteria.MinValue, 
                    criteria.MaxValue, 
                    criteria.WeightMatrixId,
                    ex);
                throw ex;
            }
        }
        #endregion

        #region run
        public void RunSelectedCriteria(Guid filterId)
        {
            try
            {
                var criteriaTypeOne = _companyScreeningRepository.ListSelectedCriteriaTypeOne(filterId);
                var criteriaTypeTwo = _companyScreeningRepository.ListSelectedCriteriaTypeTwo(filterId);
                var criteriaTypeThree = _companyScreeningRepository.ListSelectedCriteriaTypeThree(filterId);
                var criteriaTypeFive = _companyScreeningRepository.ListSelectedCriteriaTypeFive(filterId);
                var criteriaTypeSix = _companyScreeningRepository.ListSelectedCriteriaTypeSix(filterId);


                List<BaseCriteria> baseCriteria = new List<BaseCriteria>();
                if (criteriaTypeOne != null)
                    baseCriteria.AddRange(criteriaTypeOne);
                if (criteriaTypeTwo != null)
                    baseCriteria.AddRange(criteriaTypeTwo);
                if (criteriaTypeThree != null)
                    baseCriteria.AddRange(criteriaTypeThree);
                if (criteriaTypeFive != null)
                    baseCriteria.AddRange(criteriaTypeFive);
                if (criteriaTypeSix != null)
                    baseCriteria.AddRange(criteriaTypeSix);

                // index base criteria company IDs
                List<Tuple<BaseCriteria, Dictionary<Guid, Guid>>> baseCriteriaCompanyIds = new List<Tuple<BaseCriteria, Dictionary<Guid, Guid>>>();
                foreach (var criteria in baseCriteria.OrderBy(cr=> cr.Order))
                {
                    baseCriteriaCompanyIds.Add(new Tuple<BaseCriteria, Dictionary<Guid, Guid>>(criteria, criteria.CompaniesIdsResult.ToDictionary(c => c)));
                }

                var mergedCompanyIds = new List<Guid>();
                baseCriteria.ForEach(o => mergedCompanyIds.AddRange(o.CompaniesIdsResult));
                var companyIds = mergedCompanyIds.Distinct().ToList();

                Dictionary<Guid, Dictionary<Guid, EventIndicator>> companyEventIndicators = null;
                Dictionary<Guid, ReferenceData> referenceDataList = _clientDWRepository.ListCompaniesReferenceData(companyIds).ToDictionary(rd => rd.Id);
                Dictionary<Guid, CorporateData> corporateDataList = _clientDWRepository.ListCompaniesCorporateData(companyIds).ToDictionary(cd => cd.Id);
                Dictionary<Guid, FinancialData> financialDataList = _clientDWRepository.ListCompaniesFinancialData(companyIds).ToDictionary(fd => fd.Id);
                Dictionary<Guid, IGrouping<Guid, RPIndicator>> rpIndicatorsList = null;
                Dictionary<Guid, EsgRating> esgRatingList = null;

                // Criteria Type 2 - create dicationary of company event indicators
                if (criteriaTypeTwo!= null && criteriaTypeTwo.Count > 0)
                {
                    // get event indicators by Company IDs and criteria type 2 BaseEventIndicators
                    var indicators = _clientDWRepository.ListCompaniesEventIndicators(companyIds, criteriaTypeTwo.Select(ei => ei.BaseEventIndicatorId).ToList());

                    // index event indicators by Company ID and indicators by BaseEventIndicatorId
                    companyEventIndicators = (from cind in indicators
                              group cind by cind.CompanyId into cindG
                              let indDic = cindG.ToDictionary(ind => ind.BaseEventIndicatorId)
                              select new { CompanyId = cindG.Key, IndicatorDic = indDic })
                              .ToDictionary(cei => cei.CompanyId, cei => cei.IndicatorDic);
                }

                if (criteriaTypeFive != null && criteriaTypeFive.Count > 0)
                {
                    rpIndicatorsList = _clientDWRepository.ListCompaniesRPIndicators(companyIds, criteriaTypeFive.Select(e => e.TemplateIndicatorNumber))
                        .GroupBy(e => e.CompanyId)
                        .ToDictionary(i => i.Key);


                }

                if (criteriaTypeSix != null && criteriaTypeSix.Count > 0)
                {
                    esgRatingList = _clientDWRepository.ListCompaniesEsgRating(companyIds).ToDictionary(er => er.CompanyId);
                }


                List<ResultOutput> resultOutput = new List<ResultOutput>();

                foreach (KeyValuePair<Guid, CorporateData> corporateDataKvp in corporateDataList)
                {
                    CorporateData corporateData = corporateDataKvp.Value;

                    //EventIndicator eventIndicator = null;
                    var referenceData = referenceDataList[corporateDataKvp.Key];
                    //if (companyEventIndicators != null)
                    //    eventIndicator = companyEventIndicators[corporateDataKvp.Key];
                    var financialData = financialDataList[corporateDataKvp.Key];

                    List<CriteriaResultOutput> criteriaResultOutputList = new List<CriteriaResultOutput>();

                    foreach (Tuple<BaseCriteria, Dictionary<Guid, Guid>> criteriaInfo in baseCriteriaCompanyIds)
                    {
                        var criteria = criteriaInfo.Item1;
                        var criteriaCompanies = criteriaInfo.Item2;

                        CriteriaValueType resultCriteriaValueType = CriteriaValueType.Numeric;
                        CriteriaResultOutput criteriaResultOutput = new CriteriaResultOutput();
                        switch (criteria.CriteriaType)
                        {
                            case CriteriaTypeEnum.CriteriaType1:
                                resultCriteriaValueType = CriteriaValueType.Numeric;
                                criteriaResultOutput.NumericValue = TryParseNullableInt(GetValueForResultOutputCriteria(ReturnCriteriaEntityFieldFromString(criteria.CriteriaEntityField), corporateData, referenceData, financialData, null));
                                break;

                            case CriteriaTypeEnum.CriteriaType2:
                                {
                                // get criteria type 2
                                var criteria2 = (CriteriaTypeTwo)criteria;

                                // get criteria type 2 associated event indicator
                                EventIndicator eventIndicator = null;
                                Dictionary<Guid, EventIndicator> eventIndicatorDic = null;
                                if (companyEventIndicators.TryGetValue(corporateData.Id, out eventIndicatorDic))
                                {
                                    // get event indicator from dictionary
                                    eventIndicatorDic.TryGetValue(criteria2.BaseEventIndicatorId, out eventIndicator);
                                }

                                resultCriteriaValueType = CriteriaValueType.Numeric;
                                criteriaResultOutput.NumericValue = eventIndicator != null ? eventIndicator.AnswerCategory : 0;
                                }
                                break;

                            case CriteriaTypeEnum.CriteriaType3:
                                resultCriteriaValueType = CriteriaValueType.String;
                                var stringValue = GetValueForResultOutputCriteria(ReturnCriteriaEntityFieldFromString(criteria.CriteriaEntityField), corporateData, referenceData, financialData, null);
                                criteriaResultOutput.StringValue = stringValue != null ? stringValue.ToString() : null;
                                break;

                            case CriteriaTypeEnum.CriteriaType5:
                                {
                                    resultCriteriaValueType = CriteriaValueType.String;
                                    if (rpIndicatorsList != null)
                                    {

                                        if (rpIndicatorsList.ContainsKey(corporateDataKvp.Key))
                                        {
                                            var rpLlist = rpIndicatorsList[corporateDataKvp.Key].ToList();
                                            var rpi = rpLlist.FirstOrDefault( e => e.Number == ((CriteriaTypeFive) criteria).TemplateIndicatorNumber);
                                            if (rpi != null && rpi.SelectedAnswer != null)
                                                criteriaResultOutput.StringValue = rpi.SelectedAnswer.Text;
                                        }
                                    }
                                }
                                break;
                            case CriteriaTypeEnum.CriteriaType6:
                                resultCriteriaValueType = CriteriaValueType.Numeric;
                                var esgRating = esgRatingList.ContainsKey(corporateDataKvp.Key) ? esgRatingList[corporateDataKvp.Key] : null;

                                if (esgRating == null)
                                    criteriaResultOutput.NumericValue = null;
                                else
                                {
                                    criteriaResultOutput.NumericValue = TryParseNullableInt(
                                                                            GetValueForResultOutputCriteria(
                                                                                ReturnCriteriaEntityFieldFromString(criteria.CriteriaEntityField),
                                                                                corporateData,
                                                                                referenceData,
                                                                                financialData,
                                                                                esgRating
                                                                            )
                                                                        );
                                }
                                break;
                            default:
                                break;
                        }
                        criteriaResultOutput.Name = criteria.Name;
                        criteriaResultOutput.ValueType = resultCriteriaValueType;
                        criteriaResultOutput.IsValid = criteriaCompanies.ContainsKey(corporateData.Id);

                        criteriaResultOutputList.Add(criteriaResultOutput);
                    }

                    resultOutput.Add(new ResultOutput()
                    {
                        CompanyId = corporateData.Id,
                        CompanyName = corporateData.CompanyName,
                        Exchange = referenceData.Exchanges.FirstOrDefault(),
                        FilterId = filterId,
                        Id = Guid.NewGuid(),
                        Ticker = referenceData.Tickers.FirstOrDefault(),
                        Criteria = criteriaResultOutputList,
                        IsValid = criteriaResultOutputList != null ? criteriaResultOutputList.All(c => c.IsValid) : false
                    });
                }

                _companyScreeningRepository.SaveResultOutput(filterId, resultOutput);
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

        public PagedList<ResultOutput> GetResultOutput(ResultOutputFilter filter, PagingOptions pagingOptions, string orderBy = null, bool asc = true)
        {
            try
            {
                return _companyScreeningRepository.GetResultOutput(filter, pagingOptions, orderBy, asc);
            }
            catch (Exception)
            {
                throw;
            }
        }

        public List<ResultOutput> GetFullResultOutput(ResultOutputFilter filter)
        {
            try
            {
                return _companyScreeningRepository.GetFullResultOutput(filter);
            }
            catch (Exception)
            {
                throw;
            }
        }

        public void ClearResultOutput(Guid filterId)
        {
            try
            {                
                CompanyScreeningEventSource.Log.ClearResultOutput(filterId);
                _companyScreeningRepository.DeleteResultOutput(filterId);
                CompanyScreeningEventSource.Log.ClearResultOutputSuccess(filterId);
            }
            catch (Exception ex)
            {
                CompanyScreeningEventSource.Log.ClearResultOutputError(filterId, ex);
                throw;
            }
        }

        #endregion 

        #region update criteria

        public void UpdateCriteriaTypeFive(string username, Guid criteriaId, int? score, string templateIndicatorNumber)
        {
            CompanyScreeningEventSource.Log.UpdateCriteriaTypeFiveService(criteriaId);
            this.FindCurrentFilterAndIncrementVersion(username);
            _companyScreeningRepository.UpdateCriteriaTypeFive(criteriaId, score, templateIndicatorNumber);
        }

        public void UpdateCriteriaTypeOne(string username, Guid criteriaId, int minValue, int maxValue)
        {
            try
            {
                this.FindCurrentFilterAndIncrementVersion(username);
                _companyScreeningRepository.UpdateCriteriaTypeOne(criteriaId, minValue, maxValue);
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

        public void UpdateCriteriaTypeThree(string username, Guid criteriaTemplateId, string collectionName, List<string> selectedItems)
        {
            try
            {
                this.FindCurrentFilterAndIncrementVersion(username);
                _companyScreeningRepository.UpdateCriteriaTypeThree(criteriaTemplateId, collectionName, selectedItems);
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

        public void UpdateCriteriaTypeTwo(string username, Guid criteriaId, int? score)
        {
            CompanyScreeningEventSource.Log.UpdateCriteriaTypeTwoService(criteriaId);
            this.FindCurrentFilterAndIncrementVersion(username);
            _companyScreeningRepository.UpdateCriteriaTypeTwo(criteriaId, score);
        }

        public void UpdateSelectedCriteriaOrderNumber(string username, Guid filterId, List<Guid> criteriaIds)
        {
            try
            {
                this.FindCurrentFilterAndIncrementVersion(username);
                _companyScreeningRepository.UpdateSelectedCriteriaOrderNumber(filterId, criteriaIds);
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

        public void UpdateCriteriaTypeSix(Guid criteriaId, int minValue, int maxValue, string username)
        {
            try
            {
                this.FindCurrentFilterAndIncrementVersion(username);
                _companyScreeningRepository.UpdateCriteriaTypeSix(criteriaId, minValue, maxValue);
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }
        #endregion 

        #region load filter

        public List<UserFilterDetails> GetLastUsedFilters(string userName)
        {
            var user = GetUserCached(userName, false);
            if (user == null)
            {
                throw new UnknownUserException { UserName = userName };
            }

            var results = this._companyScreeningRepository.GetRootFiltersByLatestAction(user.Id, ScreeningFilterAction.Load, 5);

            return results
                .Select(r => new UserFilterDetails { UserId = user.Id, LastUsed = r.Item1.Created, Details =  ComputeFilterDetails(r.Item2).Filter })
                .ToList();
        }

        public List<UserFilterDetails> GetUsersFilters(string userName)
        {
            Guard.ArgumentNotNullOrEmpty(userName, "userName");

            var user = GetUserCached(userName, true);
            if (user == null)
            {
                throw new UnknownUserException { UserName = userName };
            }

            //get owned filters
            var ownedFilters = this._companyScreeningRepository.GetRootFiltersByOwner(new[] { user.Id }.ToList(), true, null);
            IEnumerable<RootFilter> filters = ownedFilters;
            if (user.AccountId.HasValue)
            {
                ///todo check to see if pagination is needed;
                var usersCount = this._accountsRepository.GetUsersByAccountIdTotalCount(Status.Active, user.AccountId.Value);
                ListOptions listOption = new ListOptions();
                listOption.Take = usersCount;
                var users = this._accountsRepository.GetUsers(new List<Guid>() { user.AccountId.Value }, Status.Active, "", listOption);

                var usersIds = (from u in users
                                where u.Id != user.Id
                                select u.Id).ToList();

                ///todo check to see if pagination is needed;
                var accountFilters = this._companyScreeningRepository.GetRootFiltersByOwner(usersIds, true, FilterType.Shared);
                if (accountFilters != null)
                {
                    filters = ownedFilters.Union(accountFilters).ToList();
                }
            }

            var filterStatistics = this._companyScreeningRepository.GetLastActionFilterLogs(user.Id, filters.Select(rf => rf.Id).ToList(), ScreeningFilterAction.Load);
            var statisticsDictionary = new Dictionary<Guid, LastActionFilterLog>();
            foreach (var log in filterStatistics)
            {
                statisticsDictionary[log.RootFilterId] = log;
            }

            return filters
                    .Select(rf => ComputeUserFilterDetails(user, statisticsDictionary, rf))
                    .ToList();
        }

        private UserFilterDetails ComputeUserFilterDetails(User user, Dictionary<Guid, LastActionFilterLog> statisticsDictionary, RootFilter rf)
        {
            LastActionFilterLog log;
            DateTime? lastUsed = null;
            if (statisticsDictionary.TryGetValue(rf.Id, out log))
            {
                lastUsed = log.LastActionDate;
            }

            return new UserFilterDetails { Details = ComputeFilterDetails(rf).Filter, UserId = user.Id, LastUsed = lastUsed };
        }

        #endregion load filter
       
        #region current filter operations

        public CurrentFilterDetails GetCurrentFilterDetails(string username)
        {
            try
            {
                CompanyScreeningEventSource.Log.GetCurrentFilter();

                var currentFilter = _companyScreeningRepository.GetCurrentFilter(username);
                if (currentFilter == null)
                    return null;

                var rootFilter = _companyScreeningRepository.GetRootFilter(currentFilter.RootFilterId);

                if (rootFilter == null)
                    return null;

                return ComputeFilterDetails(currentFilter, rootFilter);
            }
            catch (Exception)
            {
                throw;
            }
        }
  

        /// <summary>
        /// load the criteria of a root filter the current filter
        /// </summary>
        /// <param name="userName">the userName for the user in whose currentFilter the criteria are loaded</param>
        /// <param name="rootFilterId">the root filters's id of whoese criteria is to be loaded</param>
        /// <returns></returns>
        public CurrentFilterDetails LoadSavedFilter(string userName, Guid rootFilterId)
        {
            var utcNow = DateTime.UtcNow;

            User currentUser = this.GetUserCached(userName, true);
            if (currentUser == null)
                throw new UnknownUserException { UserName = userName };

            var currentFilter = this._companyScreeningRepository.GetCurrentFilter(currentUser.Id);

            if (currentFilter == null)
                throw new UnknownFilterException { UserName = userName };

            if (currentUser.Id != currentFilter.Id)
            {
                throw new AuthorizationException();
            }

            RootFilter rootFilter = null;
            List<ScreeningFilterAction> rootFilterActionsLog = new List<ScreeningFilterAction>();
            rootFilter = this._companyScreeningRepository.GetRootFilter(rootFilterId);

            if (rootFilter == null)
            {
                CompanyScreeningEventSource.Log.CurrentFilterInconsistenData(currentFilter.FilterId, currentFilter.Id, currentFilter.RootFilterId);
                throw new InconsistentDataException(string.Format("RootFilter with Id: {0} not found. for the CurrentFilter:{0}", currentFilter.RootFilterId, currentFilter.FilterId));
            }

            Filter filter = this.CopyAndSaveFilter(utcNow, rootFilter.FilterId, currentUser.Id, null, true);

            rootFilterActionsLog.Add(ScreeningFilterAction.Load);

            ///mark the load operation completed and make the criteria available in the current filter
            var previousFilterId = currentFilter.FilterId; // this will be deleted later;

            currentFilter.FilterId = filter.Id;
            currentFilter.RootFilterId = rootFilter.Id;
            currentFilter.Version = rootFilter.Version;

            this._companyScreeningRepository.SaveCurrentFilter(currentFilter);

            try
            {
                //delete old filter details.
                this._companyScreeningRepository.DeleteAllCriteria(previousFilterId);
                this._companyScreeningRepository.DeleteFilter(previousFilterId);

                foreach (var action in rootFilterActionsLog)
                {
                    this._companyScreeningRepository.LogScreeningFilterAction(currentUser.Id, action, rootFilter.Id);
                    this._companyScreeningRepository.Save(new LastActionFilterLog { Action = action, RootFilterId = rootFilter.Id, UserId = currentUser.Id, LastActionDate = utcNow });
                }
            }
            catch (Exception exception)
            {
                //only log the errors can be ignored here.
                CompanyScreeningEventSource.Log.PersistScreeningFilterActionWarning(exception);
            }

            return  ComputeFilterDetails(currentFilter, rootFilter);
        }

        /// <summary>
        /// assumption:
        ///  - only one save per user is executed at one time.
        ///  - each temp filter has a rootFilter
        /// </summary>
        /// <param name="userName"></param>
        /// <param name="filterId"></param>
        /// <param name="filterName"></param>
        /// <param name="shared"></param>
        /// <param name="createNew"></param>
        public RootFilterDetails SaveCurrentFilter(string userName, string filterName, bool shared, bool createNew)
        {
            filterName = filterName != null ? filterName.ToLower() : filterName;

            var filterType = shared ? FilterType.Shared : FilterType.Private;
            var utcNow = DateTime.UtcNow;

            User currentUser = this.GetUserCached(userName, true);
            if (currentUser == null)
                throw new UnknownUserException { UserName = userName };

            var currentFilter = this._companyScreeningRepository.GetCurrentFilter(currentUser.Id);

            if (currentFilter == null)
                throw new UnknownFilterException { UserName = userName };

            RootFilter rootFilter = null;
            List<ScreeningFilterAction> rootFilterActions = new List<ScreeningFilterAction>();
            if (createNew)
            {
                rootFilter = new RootFilter { Id = Guid.NewGuid() };
                rootFilter.CreatedAt = utcNow;
                rootFilterActions.Add(ScreeningFilterAction.Create);
            }
            else
            {
                rootFilter = this._companyScreeningRepository.GetRootFilter(currentFilter.RootFilterId);

                if (rootFilter == null)
                {
                    CompanyScreeningEventSource.Log.CurrentFilterInconsistenData(currentFilter.FilterId, currentFilter.Id, currentFilter.RootFilterId);
                    throw new InconsistentDataException(string.Format("RootFilter with Id: {0} not found. for the CurrentFilter:{0}", currentFilter.RootFilterId, currentFilter.FilterId));
                }
                
                
            }

            if (!createNew && (currentFilter.Id != rootFilter.OwnerUserId))
            {
                throw new AuthorizationException();
            }

            if (string.IsNullOrEmpty(filterName))
                throw new FilterValidationException { InternalCode = FilterValidationErrorCodes.NullOrEmptyName };

            if (!ValidateFilterPrivateName(filterName, currentUser, currentFilter.RootFilterId, createNew))
                throw new FilterValidationException { InternalCode = FilterValidationErrorCodes.DuplicatePrivateName };

            if (!ValidateFilterSharedName(filterName, filterType, currentUser))
                throw new FilterValidationException { InternalCode = FilterValidationErrorCodes.DuplicateSharedName };

            bool areDetailsModified = createNew;
            if (!createNew)
            {
                areDetailsModified = currentFilter.Version != rootFilter.Version;

                currentFilter = this._companyScreeningRepository.FindAndIncrementVersion<CurrentFilter>(currentUser.Id);
                if (currentFilter == null)
                {
                    CompanyScreeningEventSource.Log.CurrentFilterInconsistenData(Guid.Empty, currentUser.Id, rootFilter.Id);
                    throw new InconsistentDataException(string.Format("FindAndIncrementVersion for the user:{0} not found", currentFilter.RootFilterId, currentUser.Id));
                }
            }

            var filterDetailsId = currentFilter.FilterId;

            Guid filterId = rootFilter.FilterId;
            if (areDetailsModified)
            {
                var filter = CopyAndSaveFilter(utcNow, filterDetailsId, currentUser.Id, rootFilter.Id, false);
                filterId = filter.Id;
            }
            var previousFilterId = rootFilter.FilterId; //save filterId to delete the filter later

            ///update the rootFilter
            rootFilter.FilterId = filterId; //set the new details
            rootFilter.Name = filterName;
            rootFilter.Type = filterType;
            rootFilter.OwnerUserId = currentUser.Id;
            rootFilter.LastSaved = utcNow;
            rootFilter.Version = currentFilter.Version;
            rootFilterActions.Add(ScreeningFilterAction.Save);

            //make the filter accesible for loading : complete operation.
            this._companyScreeningRepository.SaveRootFilter(rootFilter);//the filter is now accesible
          
            if (areDetailsModified) //cleanup old details
            {
                try
                {
                    this._companyScreeningRepository.DeleteAllCriteria(previousFilterId);
                    this._companyScreeningRepository.DeleteFilter(previousFilterId);

                    foreach (var action in rootFilterActions)
                    {
                        this._companyScreeningRepository.LogScreeningFilterAction(currentUser.Id, action, rootFilter.Id);
                    }
                }
                catch (Exception exception)
                {
                    //only log the errors can be ignored here.
                    CompanyScreeningEventSource.Log.PersistScreeningFilterActionWarning(exception);
                }
            }
            return ComputeFilterDetails(rootFilter);
        }

        private Filter CopyAndSaveFilter(DateTime utcNow, Guid? sourceFilterId, Guid ownerUserId, Guid? rootFilterId, bool isCurrent)
        {
            Filter filter = new Filter { Id = Guid.NewGuid(), CreatedAt = utcNow, IsCurrent = isCurrent, OwnerUsedId = ownerUserId, RootFilterId = rootFilterId };
            ///save filter details
            this._companyScreeningRepository.SaveFilter(filter);
            if (sourceFilterId != null)
            {
                var sourceFilterIdValue = sourceFilterId.Value;
                SaveCriteria(CreateCriteria(ListSelectedCriteriaTypeOne(sourceFilterIdValue), filter));
                SaveCriteria(CreateCriteria(ListSelectedCriteriaTypeTwo(sourceFilterIdValue), filter));
                SaveCriteria(CreateCriteria(ListSelectedCriteriaTypeThree(sourceFilterIdValue), filter));
                SaveCriteria(CreateCriteria(ListSelectedCriteriaTypeFive(sourceFilterIdValue), filter));
                SaveCriteria(CreateCriteria(ListSelectedCriteriaTypeSix(sourceFilterIdValue), filter));
            }

            this._companyScreeningRepository.SaveFilter(filter);
            return filter;
        }
       
        #endregion filter operations

        #region filter validation

        private bool ValidateFilterPrivateName(string filterName, User currentUser, Guid? rootFilterId, bool createNew)
        {
            var ids = new List<Guid>();
            ids.Add(currentUser.Id);

            var count = this._companyScreeningRepository.GetRootFiltersCount(ids.ToList(), filterName, null, createNew ? null : rootFilterId, 0, 2);

            return count < 1;
        }

        private bool ValidateFilterSharedName(string filterName, FilterType filterType, User currentUser)
        {
            Guid currentUserId = currentUser.Id;
            if (filterType == FilterType.Shared && currentUser.AccountId != null)
            {
                var accountId = currentUser.AccountId.Value;
                var usersCount = this._accountsRepository.GetUsersByAccountIdTotalCount(Status.Active, accountId);
                int pageSize = 100;
                int skip = 0;
                int page = 0;

                //set max iteration if is GetUsers doesnt play.
                int maxIteratios = usersCount / pageSize + 1;
                for (int i = 0; i < maxIteratios; i++)
                {
                    ListOptions listOption = new ListOptions();
                    listOption.Take = pageSize;
                    listOption.Skip = skip;

                    var accountsUsers = this._accountsRepository.GetUsers(new List<Guid>() { currentUser.AccountId.Value }, Status.Active, "", listOption);
                    if (accountsUsers == null || accountsUsers.Count == 0)
                    {
                        break;
                    }
                    var ids = from user in accountsUsers
                              where user.Id != currentUserId
                              select user.Id;

                    var count = this._companyScreeningRepository.GetRootFiltersCount(ids.ToList(), filterName, FilterType.Shared, null, 0, 1);

                    if (count > 0)
                    {
                        return false;
                    }
                    ++page;
                    skip = pageSize * page;
                }
            }

            return true;
        }
   
        #endregion filter validation

        #region create criteria

        private void CopyBaseCriteria(BaseCriteria source, BaseCriteria destination)
        {
            destination.CompaniesIdsResult = null;
            destination.CriteriaEntity = source.CriteriaEntity;
            destination.CriteriaEntityField = source.CriteriaEntityField;
            destination.CriteriaLevel2Id = source.CriteriaLevel2Id;
            destination.CriteriaType = source.CriteriaType;
            destination.Name = source.Name;
            destination.Order = source.Order;

            //the copy is not connected
            destination.FilterId = null;
        }

        private CriteriaTypeOne CreateCriteria(CriteriaTypeOne prototype)
        {
            CriteriaTypeOne criteria = new CriteriaTypeOne();
            CopyBaseCriteria(prototype, criteria);
            criteria.Id = Guid.NewGuid();

            criteria.MaxValue = prototype.MaxValue;
            criteria.MinValue = prototype.MinValue;

            return criteria;
        }

        private CriteriaTypeTwo CreateCriteria(CriteriaTypeTwo prototype)
        {
            CriteriaTypeTwo criteria = new CriteriaTypeTwo();

            CopyBaseCriteria(prototype, criteria);
            criteria.Id = Guid.NewGuid();

            criteria.Score = prototype.Score;
            criteria.BaseEventIndicatorId = prototype.BaseEventIndicatorId;

            return criteria;
        }

        private CriteriaTypeThree CreateCriteria(CriteriaTypeThree prototype)
        {
            CriteriaTypeThree criteria = new CriteriaTypeThree();

            CopyBaseCriteria(prototype, criteria);
            criteria.Id = Guid.NewGuid();

            criteria.CollectionName = prototype.CollectionName;

            if (prototype.SelectedItems != null)
                criteria.SelectedItems = new List<string>(prototype.SelectedItems);

            return criteria;
        }

        private CriteriaTypeFive CreateCriteria(CriteriaTypeFive prototype)
        {
            CriteriaTypeFive criteria = new CriteriaTypeFive();

            CopyBaseCriteria(prototype, criteria);
            criteria.Id = Guid.NewGuid();

            criteria.Score = prototype.Score;
            criteria.TemplateIndicatorNumber = prototype.TemplateIndicatorNumber;
            if (prototype.AnswerCategories != null)
                criteria.AnswerCategories = new List<AnswerCategory>(prototype.AnswerCategories);
            return criteria;
        }

        private CriteriaTypeSix CreateCriteria(CriteriaTypeSix prototype)
        {
            CriteriaTypeSix criteria = new CriteriaTypeSix();
            CopyBaseCriteria(prototype, criteria);
            criteria.Id = Guid.NewGuid();

            criteria.MaxValue = prototype.MaxValue;
            criteria.MinValue = prototype.MinValue;
            criteria.WeightMatrixId = prototype.WeightMatrixId;

            return criteria;
        }

        private IEnumerable<T> CreateCriteria<T>(IEnumerable<T> criteria, Filter destinationFilter) where T : BaseCriteria
        {
            if (criteria == null)
                yield break;

            object factoryObject;
            Func<T, T> factoryMethod = null;

            if (_cloneMethods.TryGetValue(typeof(T), out factoryObject))
            {
                factoryMethod = factoryObject as Func<T, T>;
            }

            if (factoryMethod == null)
                throw new ArgumentOutOfRangeException(string.Format("cannot clone criteria of type: {0} ", typeof(T)));

            foreach (var criterion in criteria.OrderBy((c) => c.Order))
            {
                var copiedCriterion = factoryMethod(criterion);
                copiedCriterion.FilterId = destinationFilter.Id;

                yield return copiedCriterion;
            }
        }

        private void SaveCriteria<T>(IEnumerable<T> criteria) where T : BaseCriteria
        {
            if (criteria == null)
                return;

            foreach (var criterion in criteria)
            {
                this._companyScreeningRepository.SaveCriteria<T>(criterion);
            }
        }

        #endregion create criteria

        #region dictionaryUtils

        //private static void AddAction<T>(Dictionary<Type, object> dictionary, Action<T> action)
        //{
        //    dictionary.Add(typeof(T), action);
        //}

        private static void AddFunc<T>(Dictionary<Type, object> dictionary, Func<T, T> func)
        {
            dictionary.Add(typeof(T), func);
        }

        #endregion dictionaryUtils

        #region utils

        private static RootFilterDetails ComputeFilterDetails(RootFilter rootFilter)
        {
            Guard.ArgumentNotNull(rootFilter, "rootFilter");

            var filterDetails =  new FilterDetails
            {
                FilterId = rootFilter.FilterId,
                Name = rootFilter.Name,
                OwnerUserId = rootFilter.OwnerUserId,
                Type = rootFilter.Type,
                RootFilterId = rootFilter.Id,
                LastSaved = rootFilter.LastSaved,
                Version = rootFilter.Version
            };

            return new RootFilterDetails { CreatedAt = rootFilter.CreatedAt, Filter = filterDetails };
        }

        private static CurrentFilterDetails ComputeFilterDetails(CurrentFilter currentFilter, RootFilter rootFilter)
        {
            Guard.ArgumentNotNull(currentFilter, "currentFilter");

            var filterDetails =  new FilterDetails
            {
                FilterId = currentFilter.FilterId,
                Name = rootFilter.Name,
                OwnerUserId = rootFilter.OwnerUserId,
                Type = rootFilter.Type,
                RootFilterId = currentFilter.RootFilterId,
                LastSaved = rootFilter.LastSaved,
                Version = currentFilter.Version
            };

            return new CurrentFilterDetails { RootVersion = rootFilter.Version, Filter = filterDetails, UserId = currentFilter.Id };
        }


        private static CriteriaTypeOne CreateCriteriaTypeOne(int minValue, int maxValue, CriteriaTypeOne templateCriteria, Guid filterId)
        {
            CompanyScreeningEventSource.Log.CreateCriteriaTypeOne(minValue, maxValue, filterId);

            var criteriaTypeOne = new CriteriaTypeOne()
            {
                Id = Guid.NewGuid(),
                Name = templateCriteria.Name,
                MaxValue = maxValue,
                MinValue = minValue,
                CriteriaEntityField = templateCriteria.CriteriaEntityField,
                CriteriaEntity = templateCriteria.CriteriaEntity,
                CompaniesIdsResult = null,
                CriteriaLevel2Id = templateCriteria.CriteriaLevel2Id,
                CriteriaType = CriteriaTypeEnum.CriteriaType1,
                FilterId = filterId
            };

            return criteriaTypeOne;
        }

        private static CriteriaTypeSix CreateCriteriaTypeSix(int minValue, int maxValue, Guid matrixId, CriteriaTypeSix templateCriteria, Guid filterId)
        {
            CompanyScreeningEventSource.Log.CreateCriteriaTypeSix(minValue, maxValue, matrixId, filterId);

            var criteriaTypeSix = new CriteriaTypeSix()
            {
                Id = Guid.NewGuid(),
                Name = templateCriteria.Name,
                MaxValue = maxValue,
                MinValue = minValue,
                CriteriaEntityField = templateCriteria.CriteriaEntityField,
                CriteriaEntity = templateCriteria.CriteriaEntity,
                CompaniesIdsResult = null,
                CriteriaLevel2Id = templateCriteria.CriteriaLevel2Id,
                CriteriaType = CriteriaTypeEnum.CriteriaType6,
                FilterId = filterId,
                WeightMatrixId = matrixId
            };

            return criteriaTypeSix;
        }

        private List<CriteriaChartData> ComputeClusters(CriteriaTypeOne criteriaTypeOne)
        {
            try
            {
                CompanyScreeningEventSource.Log.LoadCriteriaTypeOneData(criteriaTypeOne.CriteriaEntity, criteriaTypeOne.CriteriaEntityField);

                var numberOfClusters = int.Parse(ConfigurationManager.AppSettings["numberOfClusters"]);
                List<CriteriaChartData> data = new List<CriteriaChartData>();
                var rawData = _clientDWRepository.LoadCriteriaTypeOneData(criteriaTypeOne.CriteriaEntity, criteriaTypeOne.CriteriaEntityField, new List<int>());
                int zeroNumbers = 0;
                var logarithmValues = new List<double>();

                rawData.ForEach((raw) =>
                {
                    if (raw == 0)
                    {
                        zeroNumbers++;
                        return;
                    }

                    var absoluteValue = Math.Abs(raw);
                    var logarithmValue = Math.Log(absoluteValue);
                    logarithmValues.Add(raw < 0 ? -logarithmValue : logarithmValue);
                });

                var clusterSize = (logarithmValues.Max() - logarithmValues.Min()) / numberOfClusters;

                CompanyScreeningEventSource.Log.ComputeClusters(criteriaTypeOne.CriteriaEntity, criteriaTypeOne.CriteriaEntityField);
                List<Cluster> clusters = new List<Cluster>();
                clusters.Add(new Cluster() { Start = logarithmValues.Min(), End = logarithmValues.Min() + clusterSize, CompanyCount = 0 });
                for (int i = 1; i < numberOfClusters; i++)
                {
                    clusters.Add(new Cluster() { Start = clusters.Last().End, End = clusters.Last().End + clusterSize, CompanyCount = 0 });
                }

                foreach (double lnValue in logarithmValues)
                {
                    var clust = clusters.Where(c => lnValue > c.Start && lnValue <= c.End).SingleOrDefault();
                    if (clust == null)
                        clusters.First().CompanyCount++;
                    else
                        clust.CompanyCount++;
                }

                var zeroClust = clusters.Where(c => c.Start < 0 && c.End >= 0).SingleOrDefault();
                if (zeroClust != null)
                    zeroClust.CompanyCount += zeroNumbers;

                data.Add(new CriteriaChartData()
                {
                    XAxisValue = clusters.First().Start < 0 ?
                                 -(int)Math.Exp(Math.Abs(clusters.First().Start)) :
                                 (int)Math.Exp(Math.Abs(clusters.First().Start)),
                    YAxisValue = clusters.First().CompanyCount
                });

                for (int i = 1; i < clusters.Count - 1; i++)
                {
                    var currentCluster = clusters[i];
                    var minValue = currentCluster.Start < 0 ? -(int)Math.Exp(Math.Abs(currentCluster.Start)) : (int)Math.Exp(Math.Abs(currentCluster.Start));
                    var maxValue = currentCluster.End < 0 ? -(int)Math.Exp(Math.Abs(currentCluster.End)) : (int)Math.Exp(Math.Abs(currentCluster.End));

                    data.Add(new CriteriaChartData()
                    {
                        XAxisValue = (minValue + maxValue) / 2,
                        YAxisValue = currentCluster.CompanyCount
                    });
                }

                data.Add(new CriteriaChartData()
                {
                    XAxisValue = clusters.Last().End < 0 ?
                                 -(int)Math.Exp(Math.Abs(clusters.Last().End)) :
                                 (int)Math.Exp(Math.Abs(clusters.Last().End)),
                    YAxisValue = clusters.Last().CompanyCount
                });

                var trimmedData = data.Where(c => c.YAxisValue > 0).ToList();

                //Concates companies which have the same xaxisvalue.
                List<CriteriaChartData> summedData = new List<CriteriaChartData>();
                trimmedData.ForEach((td) =>
                {
                    var sd = summedData.Where(s => s.XAxisValue == td.XAxisValue).SingleOrDefault();
                    if (sd == null)
                        summedData.Add(td);
                    else
                        sd.YAxisValue += td.YAxisValue;
                });

                //Sum the companies
                //var countCompanies = trimmedData.Sum(c => c.YAxisValue);

                return summedData;
            }
            catch (Exception ex)
            {
                CompanyScreeningEventSource.Log.LoadCriteriaTypeOneDataError(criteriaTypeOne.CriteriaEntity, criteriaTypeOne.CriteriaEntityField, ex);
                throw ex;
            }
        }

        private List<CriteriaChartData> ComputeCriteriaTypeSixData(CriteriaTypeSix criteriaTypeSix)
        {
            try
            {
                CompanyScreeningEventSource.Log.LoadCriteriaTypeSixData(criteriaTypeSix.CriteriaEntity, criteriaTypeSix.CriteriaEntityField);

                List<CriteriaChartData> dataList = new List<CriteriaChartData>();
                var rawData = _clientDWRepository.LoadCriteriaTypeSixData(criteriaTypeSix.CriteriaEntity, criteriaTypeSix.CriteriaEntityField, new List<int>(), criteriaTypeSix.WeightMatrixId);
                if (rawData == null || rawData.Count == 0)
                    return new List<CriteriaChartData>();

                var minData = Math.Round(rawData.Min(), MidpointRounding.AwayFromZero);
                var maxData = Math.Round(rawData.Max(), MidpointRounding.AwayFromZero);

                for (int i = (int)minData; i <= (int)maxData; i++)
                {
                    dataList.Add(new CriteriaChartData() { XAxisValue = i, YAxisValue = 0 });
                }

                rawData.ForEach((data) =>
                {
                    var dl = dataList.Where(d => d.XAxisValue == Math.Round(data, MidpointRounding.AwayFromZero)).SingleOrDefault();
                    if (dl != null)
                        dl.YAxisValue++;
                });

                return dataList;
            }
            catch (Exception ex)
            {
                CompanyScreeningEventSource.Log.LoadCriteriaTypeSixDataError(criteriaTypeSix.CriteriaEntity, criteriaTypeSix.CriteriaEntityField, criteriaTypeSix.WeightMatrixId, ex);
                throw ex;
            }
        }

        private User GetUserCached(string userName, bool forceRefresh)
        {
            var cache = MemoryCache.Default;
            User user;
            if (!forceRefresh)
            {
                user = cache.Get(userName) as User;
                if (user != null)
                {
                    return user;
                }
            }

            user = _accountsRepository.GetUserByUsername(userName);
            if (user == null)
            {
                return null;
            }

            var policy = new CacheItemPolicy { SlidingExpiration = TimeSpan.FromMinutes(5) };
            cache.Set(userName, user, policy);

            return user;
        }

        private object GetValueForResultOutputCriteria(CriteriaEntityField criteriaEntityField, CorporateData corporateData, ReferenceData referenceData, FinancialData financialData, EsgRating esgRating)
        {
            switch (criteriaEntityField)
            {
                case CriteriaEntityField.CompanyName:
                    return corporateData.CompanyName;

                case CriteriaEntityField.RootPeerGroup:
                    return corporateData.RootPeerGroup;

                case CriteriaEntityField.Region:
                    return corporateData.Region;

                case CriteriaEntityField.Country:
                    return corporateData.Country;

                case CriteriaEntityField.CompanyType:
                    return corporateData.CompanyType;

                case CriteriaEntityField.GICSSector:
                    return corporateData.GICSSector;

                case CriteriaEntityField.GICSIndustryGroup:
                    return corporateData.GICSIndustryGroup;

                case CriteriaEntityField.GICSIndustry:
                    return corporateData.GICSIndustry;

                case CriteriaEntityField.GICSSubIndustry:
                    return corporateData.GICSSubIndustry;

                case CriteriaEntityField.TotalEmployees:
                    return corporateData.TotalEmployees;

                case CriteriaEntityField.MarketCapitalization:
                    return financialData.MarketCapitalization.GetValueOrDefault();

                case CriteriaEntityField.TotalRevenues:
                    return financialData.TotalRevenues.GetValueOrDefault();

                case CriteriaEntityField.NetIncome:
                    return financialData.NetIncome.GetValueOrDefault();

                case CriteriaEntityField.NEBT:
                    return financialData.NEBT.GetValueOrDefault();

                case CriteriaEntityField.Exchanges:
                    return referenceData.Exchanges.FirstOrDefault();

                case CriteriaEntityField.Tickers:
                    return referenceData.Tickers.FirstOrDefault();

                case CriteriaEntityField.ISINs:
                    return referenceData.ISINs.FirstOrDefault();

                case CriteriaEntityField.Sedols:
                    return referenceData.Sedols.FirstOrDefault();

                case CriteriaEntityField.CUSIPs:
                    return referenceData.CUSIPs.FirstOrDefault();

                case CriteriaEntityField.TotalEsgScore:
                    return Math.Round(esgRating.TotalEsgScore, MidpointRounding.AwayFromZero);

                case CriteriaEntityField.EnvironmentScore:
                    return Math.Round(esgRating.EnvironmentScore, MidpointRounding.AwayFromZero);

                case CriteriaEntityField.SocialScore:
                    return Math.Round(esgRating.SocialScore, MidpointRounding.AwayFromZero);

                case CriteriaEntityField.GovernanceScore:
                    return Math.Round(esgRating.GovernanceScore, MidpointRounding.AwayFromZero);

                case CriteriaEntityField.PreparednessScore:
                    return Math.Round(esgRating.PreparednessScore, MidpointRounding.AwayFromZero);

                case CriteriaEntityField.DisclosureScore:
                    return Math.Round(esgRating.DisclosureScore, MidpointRounding.AwayFromZero);

                case CriteriaEntityField.QualitativePerformanceScore:
                    return Math.Round(esgRating.QualitativePerformanceScore, MidpointRounding.AwayFromZero);

                case CriteriaEntityField.QuantitativePerformanceScore:
                    return Math.Round(esgRating.QuantitativePerformanceScore, MidpointRounding.AwayFromZero);
                default:
                    break;
            }

            return null;
        }

        private CriteriaEntityField ReturnCriteriaEntityFieldFromString(string value)
        {
            return (CriteriaEntityField)Enum.Parse(typeof(CriteriaEntityField), value, true);
        }

        private void SaveCriteriaResults(Guid criteriaId, CriteriaTypeEnum criteriaType, List<Guid> companyIds)
        {
            try
            {
                CompanyScreeningEventSource.Log.SaveCriteriaResults(criteriaId, (int)criteriaType, companyIds.Count);
                _companyScreeningRepository.SaveCriteriaResults(criteriaId, criteriaType, companyIds);
            }
            catch (Exception ex)
            {
                CompanyScreeningEventSource.Log.SaveCriteriaResultsError(criteriaId, (int)criteriaType, companyIds.Count, ex);
                throw ex;
            }
        }

        private int? TryParseNullableInt(object val)
        {
            string stringVal = null;
            if (val != null)
                stringVal = val.ToString();

            int outValue;
            return int.TryParse(stringVal, out outValue) ? (int?)outValue : null;
        }

        private double? TryParseNullableDouble(object val)
        {
            string stringVal = null;
            if (val != null)
                stringVal = val.ToString();

            double outValue;
            return double.TryParse(stringVal, out outValue) ? (double?)outValue : null;
        }
     
        #endregion 
    
    }
}